/**
  ******************************************************************************
  * @file    eeprom_emulation.c
  * @author  ashuai0110
  * @version V2.4
  * @date    2024-09-25
  * @brief   FLASH模拟EEPROM
  *          (变长存储,磨损均衡,多页管理,掉电安全)
  *
  ******************************************************************************
  * @attention
  *
  * 版权声明:内容为编者(ashuai0110)原创,使用请注明出处,当然,你也可以不这样做^_^
  * 出处链接:https://gitee.com/ashuai0110/mcu_reuse_development_module.git
  *
  * Change Logs:
  * Date          Author        Notes
  * 2024-09-25    ashuai0110    完成基本内容
  *
  ******************************************************************************
  */

/* 包含头文件-----------------------------------------------------------------*/
#include "eeprom_emulation.h"

/**
 * @addtogroup modules
 * @{
 */

/**
 * @defgroup eeprom_emulation eeprom_emulation
 * @{
 */

/* 私有宏定义-----------------------------------------------------------------*/
/**
 * @defgroup eeprom_emulation_local_macros eeprom emulation local macros
 * @{
 */

/**
 * @defgroup eeprom_emulation_check_parameters_validity eeprom emulation check parameters validity
 * @{
 */
/* FLASH单次写入字节有效性检查 */
#define IS_VALID_ONCE_WR_SIZE(x)                                              \
(       ((x) == 1u)                    ||                                     \
        ((x) == 2u)                    ||                                     \
        ((x) == 4u))
/* FLASH页大小有效性检查 */
#define IS_VALID_PAGE_SIZE(x)                                                 \
(       (x) != 0u)
/**
 * @}
 */

/**
 * @defgroup eeprom_page_states eeprom page states
 * @{
 */
#define EE_PAGE_ERASED                              (0xFFu) /*!< 初始状态,被擦除过 */
#define EE_PAGE_RECEIVE                             (0xEEu) /*!< 接收状态,接收其他页的数据 */
#define EE_PAGE_VALID                               (0x00u) /*!< 有效状态,可以写入 */
/**
 * @}
 */

/**
 * @defgroup eeprom_operation eeprom operation
 * @{
 */
#define EE_WRITE_IN_VALID                           (0u) /*!< 写入有效页 */
#define EE_READ_FROM_VALID                          (1u) /*!< 从有效页读 */
/**
 * @}
 */

/**
 * @}
 */

/* 私有类型定义---------------------------------------------------------------*/
/**
 * @defgroup eeprom_emulation_local_types eeprom emulation local types
 * @{
 */

/* /--------------数据存储格式---------------\
 * | 用户数据 | 校验码 | 数据长度 | 虚拟地址 |
 * |     n    |   1    |     1    |     2    |
 * \-----------------------------------------/ */
/**
  * @brief  数据控制块 structure definition
  */
typedef struct eeprom_em_ctrl {
    /* user data: n bytes */
    uint8_t check;     /*!< 校验码 */
    uint8_t len;      /*!< 数据长度byte */
    uint16_t virtAddr; /*!< 虚拟地址(可取0~0xFFFE,0xFFFF不可用) */
} eeprom_em_ctrl_t;

/**
 * @}
 */

/* 私有变量-------------------------------------------------------------------*/

/* 全局变量-------------------------------------------------------------------*/

/* 私有函数原型---------------------------------------------------------------*/
/**
 * @defgroup eeprom_emulation_local_functions eeprom emulation local functions
 * @{
 */

/**
  * @brief  异或和校验
  *
  * @param  data : 校验数据
  *
  * @param  len  : 校验数据长度
  *
  * @retval 返回校验结果
  */
static uint8_t check_xor(uint8_t *data, uint16_t len)
{
    uint8_t xor = 0;

    while(len--)
    {
        xor ^= *data;
        data++;
    }

    return xor;
}

/**
  * @brief  检查元素是否被编程
  *
  * @param  data       : 元素数据
  *
  * @param  len        : 元素数据长度
  *
  * @retval 返回检查结果
  *    @arg MW_RET_OK  : 元素未被编程
  *    @arg MW_RET_ERR : 元素被编程
  */
static uint8_t ee_check_element(uint8_t *data, uint8_t len)
{
    uint8_t tmp = 0;
    
    while(tmp < len)
    {
        if(data[tmp] != 0xFF)
        {
            return MW_RET_ERR;
        }
        tmp++;
    }
    
    return MW_RET_OK;
}

/**
  * @brief  查找有效页
  *
  * @param  _ee             : EEPROM管理实例
  *
  * @param  operation       : 操作类型
  *
  * @param  pageNum         : 有效页页码
  *
  * @retval 返回检查结果
  *    @arg MW_RET_OK       : 找到有效页
  *    @arg MW_RET_ABSENT   : 未找到有效页
  *    @arg MW_RET_READ_ERR : flash读取失败
  */
static uint8_t ee_find_valid_page(eeprom_em_t *_ee, uint8_t operation, uint8_t *pageNum)
{
    uint8_t ret;
    uint8_t cnt;
    uint8_t pageState1[4] = {0};
    uint8_t pageState2[4] = {0};
    
    /* 从后往前读取管理页1中的每一页状态字 */
    cnt = _ee->pageTotal;
    while(cnt--)
    {
        ret = _ee->flashReadFn(_ee->page1AddrS + cnt * _ee->pageSize, pageState1, _ee->onceWrSize);
        if(MW_RET_OK != ret)
        {
            return MW_RET_READ_ERR;
        }
        if(EE_PAGE_ERASED != pageState1[0])
        {
            pageState1[3] = cnt;
            break;
        }
    }
    /* 从后往前读取管理页2中的每一页状态字 */
    cnt = _ee->pageTotal;
    while(cnt--)
    {
        ret = _ee->flashReadFn(_ee->page2AddrS + cnt * _ee->pageSize, pageState2, _ee->onceWrSize);
        if(MW_RET_OK != ret)
        {
            return MW_RET_READ_ERR;
        }
        if(EE_PAGE_ERASED != pageState2[0])
        {
            pageState2[3] = cnt + _ee->pageTotal;
            break;
        }
    }

    switch(operation)
    {
        case EE_WRITE_IN_VALID: /* 写数据操作 */
            if(EE_PAGE_VALID == pageState2[0])
            {
                /* 管理页2有效 管理页1接收 则管理页1可写 */
                if(EE_PAGE_RECEIVE == pageState1[0])
                {
                    *pageNum = pageState1[3];
                } /* 管理页2有效 管理页1其它状态 则管理页2可写 */
                else
                {
                    *pageNum = pageState2[3];
                }
            }
            else if(EE_PAGE_VALID == pageState1[0])
            {
                /* 管理页1有效 管理页2接收 则管理页2可写 */
                if(EE_PAGE_RECEIVE == pageState2[0])
                {
                    *pageNum = pageState2[3];
                } /* 管理页1有效 管理页2其它状态 则管理页1可写 */
                else
                {
                    *pageNum = pageState1[3];
                }
            }
            else
            {
                return MW_RET_ABSENT;
            }
            break;
        case EE_READ_FROM_VALID: /* 读数据操作 */
            /* 管理页1有效 则管理页1可读 */
            if(EE_PAGE_VALID == pageState1[0])
            {
                *pageNum = pageState1[3];
            } /* 管理页2有效 则管理页2可读 */
            else if(EE_PAGE_VALID == pageState2[0])
            {
                *pageNum = pageState2[3];
            }
            else
            {
                return MW_RET_ABSENT;
            }
            break;
        default:
            /* 其余情况管理页1可写 */
            *pageNum = pageState1[3];
            break;
    }
    
    return MW_RET_OK;
}

/**
  * @brief  数据验证写入
  *
  * @param  _ee              : EEPROM管理实例
  *
  * @param  virtAddr         : 虚拟地址(0~0xFFFE)
  *
  * @param  data             : 写入数据的缓冲区
  *
  * @param  len              : 数据长度byte
  *
  * @retval 返回写入结果
  *    @arg MW_RET_OK        : 写入成功
  *    @arg MW_RET_FULL      : 已满不可写
  *    @arg MW_RET_ABSENT    : 未找到有效页
  *    @arg MW_RET_WRITE_ERR : flash写入失败
  *    @arg MW_RET_READ_ERR  : flash读取失败
  */
static uint8_t ee_verify_write(eeprom_em_t *_ee, uint16_t virtAddr, void *data, uint8_t len)
{
    uint8_t ret;
    uint8_t pageNum;
    uint32_t address, endAddress;
    eeprom_em_ctrl_t eeCtrl;
    uint8_t readBuf[4] = {0};
    
    /* 查找可写页 */
    ret = ee_find_valid_page(_ee, EE_WRITE_IN_VALID, &pageNum);
    if(MW_RET_OK != ret)
    {
        return ret;
    }
    /* 根据页码区分属于管理页1还是管理页2 */
    if(pageNum >= _ee->pageTotal)
    {
        address = endAddress = _ee->page2AddrS;
        pageNum -= _ee->pageTotal;
    }
    else
    {
        address = endAddress = _ee->page1AddrS;
    }
    /* 获取可写页面的起始地址加上页面状态字节数 */
    address += pageNum * _ee->pageSize + _ee->onceWrSize;
    /* 获取可写页面的结束地址 */
    endAddress += (pageNum + 1) * _ee->pageSize;
    
    do{
        /* 从开头开始检查每个元素地址是否被编程 */
        while(address < endAddress)
        {
            /* 读取一个元素 */
            ret = _ee->flashReadFn(address, (void *)readBuf, _ee->onceWrSize);
            if(MW_RET_OK != ret)
            {
                return MW_RET_READ_ERR;
            }
    //        MW_DEBUG_LOG("wr check: %X-%X", address, *(uint32_t *)readBuf);
            /* 元素未被编程过 */
            if(MW_RET_OK == ee_check_element(readBuf, _ee->onceWrSize))
            {
                /* 当前页剩余数量小于写入数量 */
                if((endAddress - address) < (len + sizeof(eeprom_em_ctrl_t)))
                {
                    /* 当前页是最后一页则返回已满不可写 */
                    if((pageNum + 1) >= _ee->pageTotal)
                    {
                        return MW_RET_FULL;
                    } /* 否则则写到下一页 */
                    else
                    {
                        /* 设置下一页状态字为有效状态 */
                        memset((void *)readBuf, EE_PAGE_VALID, _ee->onceWrSize);
                        ret = _ee->flashWriteFn(endAddress, (void *)readBuf, _ee->onceWrSize);
                        if(MW_RET_OK != ret)
                        {
                            return MW_RET_WRITE_ERR;
                        }
                        address = endAddress + _ee->onceWrSize;
                    }
                }
                /* 设置控制块参数 */
                eeCtrl.check = check_xor((uint8_t *)data, len);
                eeCtrl.len = len;
                eeCtrl.virtAddr = virtAddr;
                /* 向上整数对齐防止因flash单次写入字节限制导致的数据不对齐 */
                len = MW_ALIGN_UP(len, _ee->onceWrSize);
                /* 写入用户数据 */
                ret = _ee->flashWriteFn(address, data, len);
    //            MW_DEBUG_LOG("wr write: %X", address);
                if(MW_RET_OK != ret)
                {
                    return MW_RET_WRITE_ERR;
                }
                /* 控制块写入用户数据之后 */
                ret = _ee->flashWriteFn(address + len, (void *)&eeCtrl, sizeof(eeprom_em_ctrl_t));
                if(MW_RET_OK != ret)
                {
                    return MW_RET_WRITE_ERR;
                }
                return MW_RET_OK;
            } /* 被编程过则递增 */
            else
            {
                address += _ee->onceWrSize;
            }
        }
        /* 当前页不是最后一页则写到下一页 */
        if((pageNum + 1) < _ee->pageTotal)
        {
            /* 设置下一页状态字为有效状态 */
            memset((void *)readBuf, EE_PAGE_VALID, _ee->onceWrSize);
            ret = _ee->flashWriteFn(endAddress, (void *)readBuf, _ee->onceWrSize);
            if(MW_RET_OK != ret)
            {
                return MW_RET_WRITE_ERR;
            }
            address = endAddress + _ee->onceWrSize;
            endAddress += _ee->pageSize;
        }
    }while(++pageNum < _ee->pageTotal);
    
    return MW_RET_FULL;
}

/**
  * @brief  将旧管理页指定数据迁移至新管理页
  *
  * @param  _ee              : EEPROM管理实例
  *
  * @param  virtAddr         : 虚拟地址0~0xFFFE
  *
  * @param  pageNum          : 旧管理页页码
  *
  * @retval 返回结果
  *    @arg MW_RET_OK        : 成功
  *    @arg MW_RET_FULL      : 已满不可写
  *    @arg MW_RET_WRITE_ERR : flash写入失败
  *    @arg MW_RET_READ_ERR  : flash读取失败
  */
static uint8_t ee_data_transfer(eeprom_em_t *_ee, uint16_t virtAddr, uint8_t pageNum)
{
    uint8_t ret;
    uint8_t curLen, residueLen;
    uint16_t residueNum;
    uint32_t address, startAddress, addressTmp;
    uint8_t readBuf[4] = {0};
    eeprom_em_ctrl_t eeCtrl;
    
    /* 根据页码区分属于管理页1还是管理页2 */
    if(pageNum >= _ee->pageTotal)
    {
        addressTmp = _ee->page2AddrS;
        pageNum -= _ee->pageTotal;
    }
    else
    {
        addressTmp = _ee->page1AddrS;
    }
    /* 从旧管理页的所有页里读指定数据 */
    do {
        /* 获取可读页面的起始地址加上页面状态字节数 */
        startAddress = addressTmp + pageNum * _ee->pageSize + _ee->onceWrSize;
        /* 获取可读页面的结束地址减去检查元素字节数 */
        address = addressTmp + (pageNum + 1) * _ee->pageSize - _ee->onceWrSize;
        /* 从末尾开始检查每个元素地址是否被编程 */
        while(address > startAddress)
        {
            /* 读取一个元素 */
            ret = _ee->flashReadFn(address, (void *)readBuf, _ee->onceWrSize);
            if(MW_RET_OK != ret)
            {
                return MW_RET_READ_ERR;
            }
    //        MW_DEBUG_LOG("rd check: %X-%X", address, *(uint32_t *)readBuf);
            /* 元素被编程过 */
            if(MW_RET_ERR == ee_check_element(readBuf, _ee->onceWrSize))
            {
                /* 获取控制块数据 */
                address -= (sizeof(eeprom_em_ctrl_t) - _ee->onceWrSize);
                ret = _ee->flashReadFn(address, (void *)&eeCtrl, sizeof(eeprom_em_ctrl_t));
                if(MW_RET_OK != ret)
                {
                    return MW_RET_READ_ERR;
                }
                /* 匹配虚拟地址 */
                if(eeCtrl.virtAddr == virtAddr)
                {
                    addressTmp = addressTmp == _ee->page1AddrS ? _ee->page2AddrS : _ee->page1AddrS;
                    residueNum = _ee->pageSize - _ee->transferAddr % _ee->pageSize; /* 计算剩余可写数量 */
                    residueLen = MW_ALIGN_UP(eeCtrl.len, _ee->onceWrSize); /* 用户数据数量 */
                    address -= residueLen; /* 用户数据地址 */
                    /* 可写数量小于待写入数量 */
                    if((residueLen + sizeof(eeprom_em_ctrl_t)) > residueNum)
                    {
                        addressTmp += (_ee->pageTotal - 1) * _ee->pageSize;
                        /* 当前写入页是最后一页则返回已满不可写 */
                        if(_ee->transferAddr > addressTmp)
                        {
                            return MW_RET_FULL;
                        } /* 否则则写到下一页 */
                        else
                        {
                            /* 向上整数取整得出下一页的首地址 */
                            _ee->transferAddr = MW_ALIGN_UP(_ee->transferAddr, _ee->pageSize);
                            /* 设置下一页状态字为接收状态 */
                            memset((void *)readBuf, EE_PAGE_RECEIVE, _ee->onceWrSize);
                            ret = _ee->flashWriteFn(_ee->transferAddr, (void *)readBuf, _ee->onceWrSize);
                            if(MW_RET_OK != ret)
                            {
                                return MW_RET_WRITE_ERR;
                            }
                            _ee->transferAddr += _ee->onceWrSize;
                        }
                    }
                    /* 循环读取并迁移用户数据 */
                    while(residueLen)
                    {
                        curLen = residueLen >= sizeof(readBuf) ? sizeof(readBuf) : residueLen; /* 计算可读数量 */
                        /* 读取用户数据 */
                        ret = _ee->flashReadFn(address, (void *)readBuf, curLen);
                        if(MW_RET_OK != ret)
                        {
                            return MW_RET_READ_ERR;
                        }
                        address += curLen;
                        residueLen -= curLen;
                        /* 迁移用户数据 */
                        ret = _ee->flashWriteFn(_ee->transferAddr, (void *)readBuf, curLen);
                        if(MW_RET_OK != ret)
                        {
                            return MW_RET_WRITE_ERR;
                        }
                        _ee->transferAddr += curLen;
                    }
                    /* 迁移控制块数据 */
                    ret = _ee->flashWriteFn(_ee->transferAddr, (void *)&eeCtrl, sizeof(eeprom_em_ctrl_t));
                    if(MW_RET_OK != ret)
                    {
                        return MW_RET_WRITE_ERR;
                    }
                    _ee->transferAddr += sizeof(eeprom_em_ctrl_t);
                    
                    return MW_RET_OK;
                } /* 不匹配则递减 */
                else
                {
                    address -= (MW_ALIGN_UP(eeCtrl.len, _ee->onceWrSize) + _ee->onceWrSize);
                }
            } /* 未被编程则递减 */
            else
            {
                address -= _ee->onceWrSize;
            }
        }
    }while(pageNum--);
    
    return MW_RET_OK;
}

/**
  * @brief  将旧管理页所有数据迁移至新管理页,并将未写入数据写入旧数据之后,擦除旧管理页
  *
  * @param  _ee              : EEPROM管理实例
  *
  * @param  virtAddr         : 虚拟地址(0~0xFFFE)
  *
  * @param  data             : 写入数据的缓冲区
  *
  * @param  len              : 数据长度byte
  *
  * @retval 返回结果
  *    @arg MW_RET_OK        : 成功
  *    @arg MW_RET_FULL      : 已满不可写
  *    @arg MW_RET_ABSENT    : 未找到有效页
  *    @arg MW_RET_WRITE_ERR : flash写入失败
  *    @arg MW_RET_READ_ERR  : flash读取失败
  *    @arg MW_RET_ERASE_ERR : flash擦除失败
  */
static uint8_t ee_page_transfer(eeprom_em_t *_ee, uint16_t virtAddr, void *data, uint8_t len)
{
    uint8_t ret = MW_RET_OK;
    uint8_t pageNum;
    uint8_t pageState[4] = {0};
    uint16_t virtAddrX;
    uint32_t newAddress;
    
    /* 查找有效页 */
    ret = ee_find_valid_page(_ee, EE_READ_FROM_VALID, &pageNum);
    if(MW_RET_OK != ret)
    {
        return ret;
    }
    /* 根据页码区分属于管理页1还是管理页2 */
    if(pageNum >= _ee->pageTotal)
    {
        newAddress = _ee->page1AddrS;
    }
    else
    {
        newAddress = _ee->page2AddrS;
    }
    /* 记录数据迁移地址 */
    _ee->transferAddr = newAddress + _ee->onceWrSize;
    /* 设置新管理页状态字为接收状态 */
    memset((void *)pageState, EE_PAGE_RECEIVE, _ee->onceWrSize);
    ret = _ee->flashWriteFn(newAddress, (void *)pageState, _ee->onceWrSize);
    if(MW_RET_OK != ret)
    {
        return MW_RET_WRITE_ERR;
    }
    /* 将旧管理页的所有数据写入新管理页 */
    for(virtAddrX = 0; virtAddrX < _ee->virtAddrTotal; virtAddrX++)
    {
        if(virtAddrX != virtAddr)
        {
            ret = ee_data_transfer(_ee, virtAddrX, pageNum);
            if(MW_RET_OK != ret)
            {
                return ret;
            }
        }
    }
    /* 将新数据写入新管理页 */
    if(0xFFFF != virtAddr)
    {
        ret = ee_verify_write(_ee, virtAddr, data, len);
        if(MW_RET_OK != ret)
        {
            return ret;
        }
    }
    /* 将旧管理页全部擦除 */
    ret = _ee->flashEraseFn(newAddress == _ee->page1AddrS ? _ee->page2AddrS : _ee->page1AddrS, _ee->pageSize * _ee->pageTotal);
    if(MW_RET_OK != ret)
    {
        return MW_RET_ERASE_ERR;
    }
    /* 设置新管理页状态字为有效状态 */
    memset((void *)pageState, EE_PAGE_VALID, _ee->onceWrSize);
    /* 将新管理页中所有接收状态的页的状态字改为有效状态 */
    _ee->transferAddr = MW_ALIGN_UP(_ee->transferAddr, _ee->pageSize);
    pageNum = (_ee->transferAddr - newAddress) / _ee->pageSize;
    while(pageNum--)
    {
        ret = _ee->flashWriteFn(newAddress + pageNum * _ee->pageSize, (void *)pageState, _ee->onceWrSize);
        if(MW_RET_OK != ret)
        {
            return MW_RET_WRITE_ERR;
        }
    }
    
    
    return MW_RET_OK;
}

/**
  * @brief  擦除所有页,并将管理页1状态字设置为有效状态
  *
  * @param  _ee              : EEPROM管理实例
  *
  * @retval 返回结果
  *    @arg MW_RET_OK        : 成功
  *    @arg MW_RET_WRITE_ERR : flash写入失败
  *    @arg MW_RET_ERASE_ERR : flash擦除失败
  */
static uint8_t ee_format(eeprom_em_t *_ee)
{
    uint8_t ret = MW_RET_OK;
    uint8_t pageState[4] = {0};
    
    /* 擦除管理页1 */
    ret = _ee->flashEraseFn(_ee->page1AddrS, _ee->pageSize * _ee->pageTotal);
    if(MW_RET_OK != ret)
    {
        return MW_RET_ERASE_ERR;
    }
    /* 擦除管理页2 */
    ret = _ee->flashEraseFn(_ee->page2AddrS, _ee->pageSize * _ee->pageTotal);
    if(MW_RET_OK != ret)
    {
        return MW_RET_ERASE_ERR;
    }
    /* 设置管理页1状态字为有效状态 */
    memset((void *)pageState, EE_PAGE_VALID, _ee->onceWrSize);
    ret = _ee->flashWriteFn(_ee->page1AddrS, (void *)pageState, _ee->onceWrSize);
    if(MW_RET_OK != ret)
    {
        return MW_RET_WRITE_ERR;
    }
    
    return ret;
}

/**
  * @brief  根据状态字初始化EEPROM
  *
  * @param  _ee              : EEPROM管理实例
  *
  * @retval 返回结果
  *    @arg MW_RET_OK        : 成功
  *    @arg MW_RET_WRITE_ERR : flash写入失败
  *    @arg MW_RET_READ_ERR  : flash读取失败
  *    @arg MW_RET_ERASE_ERR : flash擦除失败
  */
static uint8_t ee_init(eeprom_em_t *_ee)
{
    uint8_t ret;
    uint8_t cnt;
    uint8_t pageState1[4] = {0};
    uint8_t pageState2[4] = {0};
    
    /* 从后往前读取管理页1中的每一页状态字 */
    cnt = _ee->pageTotal;
    while(cnt--)
    {
        ret = _ee->flashReadFn(_ee->page1AddrS + cnt * _ee->pageSize, pageState1, _ee->onceWrSize);
        if(MW_RET_OK != ret)
        {
            return MW_RET_READ_ERR;
        }
        if(EE_PAGE_ERASED != pageState1[0])
        {
            break;
        }
    }
    /* 从后往前读取管理页2中的每一页状态字 */
    cnt = _ee->pageTotal;
    while(cnt--)
    {
        ret = _ee->flashReadFn(_ee->page2AddrS + cnt * _ee->pageSize, pageState2, _ee->onceWrSize);
        if(MW_RET_OK != ret)
        {
            return MW_RET_READ_ERR;
        }
        if(EE_PAGE_ERASED != pageState2[0])
        {
            break;
        }
    }
    
    switch(pageState1[0])
    {
        case EE_PAGE_ERASED:
            /* 管理页1被擦除 管理页2有效 是正常情况 */
            if(pageState2[0] == EE_PAGE_VALID)
            {
                
            } /* 管理页1被擦除 管理页2接收 要设置管理页2有效 */
            else if(pageState2[0] == EE_PAGE_RECEIVE)
            {
                memset((void *)pageState2, EE_PAGE_VALID, _ee->onceWrSize);
                ret = _ee->flashWriteFn(_ee->page2AddrS, (void *)pageState2, _ee->onceWrSize);
                if(MW_RET_OK != ret)
                {
                    return MW_RET_WRITE_ERR;
                }
            } /* 其余是异常情况 全部擦除并设置管理页1有效 */
            else
            {
                ret = ee_format(_ee);
                if(MW_RET_OK != ret)
                {
                    return ret;
                }
            }
            break;
        case EE_PAGE_RECEIVE:
            /* 管理页1接收 管理页2被擦除 要设置管理页1有效 */
            if(pageState2[0] == EE_PAGE_ERASED)
            {
                memset((void *)pageState1, EE_PAGE_VALID, _ee->onceWrSize);
                ret = _ee->flashWriteFn(_ee->page1AddrS, (void *)pageState1, _ee->onceWrSize);
                if(MW_RET_OK != ret)
                {
                    return MW_RET_WRITE_ERR;
                }
            } /* 管理页1接收 管理页2有效 要进行数据迁移且应先擦除管理页1 */
            else if(pageState2[0] == EE_PAGE_VALID)
            {
                /* 擦除管理页1 */
                ret = _ee->flashEraseFn(_ee->page1AddrS, _ee->pageSize * _ee->pageTotal);
                if(MW_RET_OK != ret)
                {
                    return MW_RET_ERASE_ERR;
                }
                /* 数据迁移 */
                ret = ee_page_transfer(_ee, 0xFFFF, NULL, 0);
                if(MW_RET_OK != ret)
                {
                    return ret;
                }
            } /* 其余是异常情况 全部擦除并设置管理页1有效 */
            else
            {
                ret = ee_format(_ee);
                if(MW_RET_OK != ret)
                {
                    return ret;
                }
            }
            break;
        case EE_PAGE_VALID:
            /* 管理页1有效 管理页2被擦除 是正常情况 */
            if(pageState2[0] == EE_PAGE_ERASED)
            {
                
            } /* 管理页1有效 管理页2接收 要进行数据迁移且应先擦除管理页2 */
            else if(pageState2[0] == EE_PAGE_RECEIVE)
            {
                /* 擦除管理页2 */
                ret = _ee->flashEraseFn(_ee->page2AddrS, _ee->pageSize * _ee->pageTotal);
                if(MW_RET_OK != ret)
                {
                    return MW_RET_ERASE_ERR;
                }
                /* 数据迁移 */
                ret = ee_page_transfer(_ee, 0xFFFF, NULL, 0);
                if(MW_RET_OK != ret)
                {
                    return ret;
                }
            } /* 其余是异常情况 全部擦除并设置管理页1有效 */
            else
            {
                ret = ee_format(_ee);
                if(MW_RET_OK != ret)
                {
                    return ret;
                }
            }
            break;
        default: /* 其余是异常情况 全部擦除并设置管理页1有效 */
            ret = ee_format(_ee);
            if(MW_RET_OK != ret)
            {
                return ret;
            }
            break;
    }
    
    return MW_RET_OK;
}

/**
 * @}
 */

/**
 * @defgroup eeprom_emulation_global_functions eeprom emulation global functions
 * @{
 */

/**
  * @brief  模拟EEPROM初始化
  *
  * @param  _ee              : EEPROM管理实例
  *
  * @param  page1AddrS       : 管理页1起始地址(管理多页时,由起始地址顺延管理剩余页)
  *
  * @param  page2AddrS       : 管理页2起始地址(管理多页时,由起始地址顺延管理剩余页)
  *
  * @param  pageSize         : FLASH单页大小byte
  *
  * @param  onceWrSize       : FLASH单次写入字节byte(可取1,2,4)
  *
  * @param  pageTotal        : 管理的页数(管理页1和管理页2管理FLASH页数必须相等,若各管理两页则此变量值为2)
  *
  * @param  virtAddrTotal    : 已使用的虚拟地址数量(用户要存储的数据总个数,一个数据对应一个虚拟地址)
  *
  * @param  flashErase       : 对接flash擦除接口(用户实现)
  *
  * @param  flashRead        : 对接flash读取接口(用户实现)
  *
  * @param  flashWrite       : 对接flash写入接口(用户实现)
  *
  * @retval 返回结果
  *    @arg MW_RET_OK        : 成功
  *    @arg MW_RET_WRITE_ERR : flash写入失败
  *    @arg MW_RET_READ_ERR  : flash读取失败
  *    @arg MW_RET_ERASE_ERR : flash擦除失败
  */
uint8_t eeprom_em_init(eeprom_em_t *_ee, uint32_t page1AddrS, uint32_t page2AddrS, uint16_t pageSize, uint8_t onceWrSize, uint8_t pageTotal, \
    uint16_t virtAddrTotal, ee_flash_erase_fn_t flashErase, ee_flash_rw_fn_t flashRead, ee_flash_rw_fn_t flashWrite)
{
    MW_ASSERT_PARAM(IS_VALID_POINTER(_ee));
    MW_ASSERT_PARAM(IS_VALID_POINTER(flashErase));
    MW_ASSERT_PARAM(IS_VALID_POINTER(flashRead));
    MW_ASSERT_PARAM(IS_VALID_POINTER(flashWrite));
    MW_ASSERT_PARAM(IS_VALID_ONCE_WR_SIZE(onceWrSize));
    MW_ASSERT_PARAM(IS_VALID_PAGE_SIZE(pageSize));
    MW_ASSERT_PARAM(IS_VALID_PAGE_SIZE(pageTotal));

    /* 初始化各参数 */
    memset(_ee, 0, sizeof(eeprom_em_t));
    _ee->page1AddrS = page1AddrS;
    _ee->page2AddrS = page2AddrS;
    _ee->pageSize = pageSize;
    _ee->onceWrSize = onceWrSize;
    _ee->pageTotal = pageTotal;
    _ee->virtAddrTotal = virtAddrTotal;
    _ee->flashEraseFn = flashErase;
    _ee->flashReadFn = flashRead;
    _ee->flashWriteFn = flashWrite;

    return ee_init(_ee);
}

/**
  * @brief  模拟EEPROM取消初始化
  *
  * @param  _ee : EEPROM管理实例
  *
  * @retval None
  */
void eeprom_em_deinit(eeprom_em_t *_ee)
{
    memset(_ee, 0, sizeof(eeprom_em_t));
}

/**
  * @brief  模拟EEPROM写入数据
  *
  * @param  _ee              : EEPROM管理实例
  *
  * @param  virtAddr         : 虚拟地址(0~0xFFFE,用户应按顺序从0递增,如存储两个用户数据,则虚拟地址应取0和1)
  *
  * @param  data             : 写入数据的缓冲区
  *
  * @param  len              : 数据长度byte
  *
  * @retval 返回写入结果
  *    @arg MW_RET_OK        : 写入成功
  *    @arg MW_RET_FULL      : 已满不可写
  *    @arg MW_RET_WRITE_ERR : flash写入失败
  *    @arg MW_RET_READ_ERR  : flash读取失败
  *    @arg MW_RET_ERASE_ERR : flash擦除失败
  */
uint8_t eeprom_em_write(eeprom_em_t *_ee, uint16_t virtAddr, void *data, uint8_t len)
{
    uint8_t ret;
    
    /* 数据验证写入 */
    ret = ee_verify_write(_ee, virtAddr, data, len);
    
    if(MW_RET_FULL == ret)
    {
        /* 数据迁移 */
        ret = ee_page_transfer(_ee, virtAddr, data, len);
        if(MW_RET_OK != ret)
        {
            return ret;
        }
    }
    
    return ret;
}

/**
  * @brief  模拟EEPROM读取数据
  *
  * @param  _ee              : EEPROM管理实例
  *
  * @param  virtAddr         : 虚拟地址(0~0xFFFE)
  *
  * @param  data             : 读取数据的存储区
  *
  * @param  len              : 数据长度
  *
  * @retval 返回写入结果
  *    @arg MW_RET_OK        : 读取成功
  *    @arg MW_RET_ERR       : 读取失败,未找到匹配数据或虚拟地址超出规定
  *    @arg MW_RET_ABSENT    : 未找到有效页
  *    @arg MW_RET_CHECK_ERR : 校验错误
  *    @arg MW_RET_READ_ERR  : flash读取失败
  */
uint8_t eeprom_em_read(eeprom_em_t *_ee, uint16_t virtAddr, void *data, uint8_t len)
{
    uint8_t ret = MW_RET_ERR;
    uint8_t pageNum;
    uint32_t address, startAddress;
    eeprom_em_ctrl_t eeCtrl;
    uint8_t readBuf[4] = {0};
    
    if(virtAddr >= _ee->virtAddrTotal)
    {
        return MW_RET_ERR;
    }
    
    /* 查找可读page */
    ret = ee_find_valid_page(_ee, EE_READ_FROM_VALID, &pageNum);
    if(MW_RET_OK != ret)
    {
        return ret;
    }
    
    /* 根据页码区分是页1还是页2 */
    if(pageNum >= _ee->pageTotal)
    {
        address = startAddress = _ee->page2AddrS;
        pageNum -= _ee->pageTotal;
    }
    else
    {
        address = startAddress = _ee->page1AddrS;
    }
    /* 获取有效页面的起始地址加上页面状态字节数 */
    startAddress += pageNum * _ee->pageSize + _ee->onceWrSize;
    /* 获取有效页面的结束地址减去检查元素字节数 */
    address += (pageNum + 1) * _ee->pageSize - _ee->onceWrSize;
    
    do{
        /* 从末尾开始检查每个元素地址是否被编程 */
        while(address > startAddress)
        {
            /* 读取一个元素 */
            ret = _ee->flashReadFn(address, (void *)readBuf, _ee->onceWrSize);
            if(MW_RET_OK != ret)
            {
                return MW_RET_READ_ERR;
            }
    //        MW_DEBUG_LOG("rd check: %X-%X", address, *(uint32_t *)readBuf);
            /* 元素被编程过 */
            if(MW_RET_ERR == ee_check_element(readBuf, _ee->onceWrSize))
            {
                /* 获取控制块数据 */
                address -= (sizeof(eeprom_em_ctrl_t) - _ee->onceWrSize);
                ret = _ee->flashReadFn(address, (void *)&eeCtrl, sizeof(eeprom_em_ctrl_t));
                if(MW_RET_OK != ret)
                {
                    return MW_RET_READ_ERR;
                }
                /* 检查虚拟地址和数据长度 */
                if(eeCtrl.virtAddr == virtAddr && eeCtrl.len == len)
                {
                    /* 读取用户数据 */
                    ret = _ee->flashReadFn(address - MW_ALIGN_UP(eeCtrl.len, _ee->onceWrSize), data, eeCtrl.len);
                    if(MW_RET_OK != ret)
                    {
                        return MW_RET_READ_ERR;
                    }
                    /* 校验码不一致则返回校验错误 */
                    if(eeCtrl.check != check_xor((uint8_t *)data, eeCtrl.len))
                    {
                        return MW_RET_CHECK_ERR;
                    }
                    return MW_RET_OK;
                } /* 否则直接递减到下一个控制块 */
                else
                {
                    address -= (MW_ALIGN_UP(eeCtrl.len, _ee->onceWrSize) + _ee->onceWrSize);
                }
            } /* 未被编程则递减 */
            else
            {
                address -= _ee->onceWrSize;
            }
        }
        /* 当前页不是第一页则读上一页 */
        if(pageNum != 0)
        {
            address = startAddress - _ee->onceWrSize - _ee->onceWrSize;
            startAddress -= _ee->pageSize;
        }
    }while(pageNum--);
    
    return MW_RET_ERR;
}


/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */
